from urllib.parse import urlencode, parse_qs
from urllib.request import urlopen
from django.conf import settings
import json
from .exceptions import QQOauthExcepiton
import logging
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer, BadData
from . import constants

# 定义日志对象
logger = logging.getLogger('django')


class QQOauth(object):
    """QQ登录工具类---面向对象封装，供视图使用"""

    def __init__(self, client_id=None, redirect_url=None, state=None, client_secret=None):
        """构造函数"""
        # 接收参数或从配置中读取参数
        self.client_id = client_id or settings.QQ_CLIENT_ID
        self.redirect_url = redirect_url or settings.QQ_REDIRECT_URI
        self.state = state or settings.QQ_STATE
        self.client_secret = client_secret or settings.QQ_CLIENT_SECRET

    def oauth_login_url(self):
        """拼接QQ扫码登录的url"""
        # 组织请求参数
        params = {
            'response_type': 'code',
            'client_id': self.client_id,
            'redirect_uri': self.redirect_url,
            'state': self.state
        }

        login_url = 'https://graph.qq.com/oauth2.0/authorize?'
        # 使用urlencode将参数转成查询字符串并与请求地址拼接
        login_url += urlencode(params)

        return login_url

    def get_access_token(self, code):
        """向QQ请求获取access_token"""
        # 构造请求参数
        params = {
            'grant_type': 'authorization_code',
            'client_id': self.client_id,
            'client_secret': self.client_secret,
            'code': code,
            'redirect_uri': self.redirect_url
        }

        # 将参数转换成查询字符串并和请求的QQ接口地址进行拼接
        url = 'https://graph.qq.com/oauth2.0/token?'
        url += urlencode(params)

        try:
            # 由服务器请求QQ接口地址,并接收响应，同文件类型，可调用read方法读取响应内容:二进制
            response = urlopen(url)
            # 得到返回的查询字符串格式：access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14
            response_str = response.read().decode()
            # 使用parse_qs将查询字符串转换成字典
            response_dict = parse_qs(response_str)
            # 获取access_token,parse_qs转换后的字典中access_token键对应的值为list格式，仅一个元素，需要通过下标取出
            access_token = response_dict.get('access_token')[0]
        except Exception as e:
            # 使用log日志系统接收错误信息
            logger.error(e)
            # 获取失败时抛出异常，并自定义异常信息
            raise QQOauthExcepiton('获取access_token')

        return access_token

    def get_openid(self, access_token):
        """向QQ服务器获取openid"""
        # 拼接查询字符串得到拼接后的接口地址
        url = 'https://graph.qq.com/oauth2.0/me?access_token='
        url += access_token

        response_str1 = ''
        try:
            # 向QQ服务器发起请求
            response = urlopen(url)
            # 获取内容:"callback( {"client_id":"101474184","openid":"625F00A392B1FAE1CD112F5B67DB7FCB"} );\n"
            response_str1 = response.read().decode()
            # 通过字符串切片获取JSON
            response_str2 = response_str1[10:-4]
            # 将JSON数据转换成字典
            response_dict = json.loads(response_str2)
            # 获取openid
            openid = response_dict.get('openid')
        except Exception as e:
            # 接口调用有错误时，会以查询字符串的方式返回code和msg
            data = parse_qs(response_str1)
            # 使用日志系统接收异常信息
            logger.error(e)
            # 抛出异常并将QQ返回异常信息内容抛出，由接口调用处进行捕获处理
            raise QQOauthExcepiton('code:%s msg:%s' % (data.get('code'), data.get('msg')))

        return openid

    @staticmethod
    def generate_access_token(openid):
        """生成绑定用户身份的access_token"""
        # 使用静态方法是因为此方法内部没有使用到self，而且静态方法可以直接通过类进行调用
        serializer = Serializer(settings.SECRET_KEY, constants.QQ_ACCESS_TOKEN_EXPIRES)
        # 生成access_token：通常是定义一个字典进行反序列化
        access_token = serializer.dumps(openid)
        # 返回access_token:转成字符串格式进行返回
        return access_token.decode()

    @staticmethod
    def check_access_token(access_token):
        """校验前端请求时携带的access_token是否正确"""
        # 将二进制access_token转成str
        # access_token = access_token.decode()
        # 定义反序列化器
        serializer = Serializer(settings.SECRET_KEY, constants.QQ_ACCESS_TOKEN_EXPIRES)
        try:
            # 校验
            openid = serializer.loads(access_token)
        except BadData:
            return False
        else:
            return openid
